﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Speech.Recognition;
using System.Windows.Forms;

using Perseus;
using Perseus.Data;
using Perseus.Plugins;

using Hetymine.Executioner.Commands;
using Hetymine.Executioner.Data;
using Hetymine.Executioner.Plugins.General.NotificationIcon;
using Hetymine.Executioner.Triggers;

namespace Hetymine.Executioner.Plugins.Triggers.Speech {
    public class Speech : TriggerPlugin, ILoader, IExecutable, IConfigurable<IniFile> {
        private SpeechConfigurator _Configurator;
        // Speech recognition
        private bool _SpeechInitialized;
        private SpeechRecognizer _SpeechRecognizer;
        private MenuItem _ContextMenu;

        private EventHandler _LoadingEvent;
        private EventHandler _LoadedEvent;


        public Speech() {
            this.PluginInfo = PluginInfo.FromAssembly(
                "Speech",
                this.GetType().Assembly,
                "Blake Hancock",
                "http://hetymine.com/executioner/plugins/general/speech/"
            );

            this._SpeechInitialized = false;

            this._LoadingEvent = new EventHandler(Executioner_Loading);
            this._LoadedEvent = new EventHandler(Executioner_Loaded);
        }

        #region Executioner Events
        private void Executioner_Loading(object sender, EventArgs e) {            
            try {                
                if (Executioner.IsPluginLoaded("Notification Icon")) {
                    this._ContextMenu = new MenuItem();
                    this._ContextMenu.Click += new EventHandler(ContextMenu_Click);
                    this._ContextMenu.Text = "Toggle Speech [Disabled]";

                    var p = Executioner.PluginService["Notification Icon"];
                    p.Invoke("AddMenuItem", "Speech", this._ContextMenu);
                }
            }
            catch {
                Executioner.Sounds.Error();
            }
        }
        private void Executioner_Loaded(object sender, EventArgs e) {
            // Turn on speech recognition if it is enabled and in use
            if (Executioner.Config.GetBool("Speech", "Enable Speech") && Executioner.TriggerTypeInUse(typeof(SpeechTrigger))) {
                this.EnableSpeech();
            }
        }
        #endregion

        private void ContextMenu_Click(object sender, EventArgs e) {
            this.ToggleSpeech();
        }

        public override ITrigger InitializeTrigger() {
            return new SpeechTrigger();
        }

        private void ToggleSpeech() {
            if (this._SpeechInitialized == true) {
                this.DisableSpeech();
                Executioner.Config.SetBool("Speech", "Enable Speech", false);
            }
            else {
                this.EnableSpeech();
                Executioner.Config.SetBool("Speech", "Enable Speech", true);
            }
        }
        private void EnableSpeech() {
            try {
                if (!this._SpeechInitialized) {
                    if (Executioner.Commands.ContainsKey("Trigger")) {
                        var speechTriggers = from c in Executioner.Commands["Trigger"]
                                             from t in ((TriggerCommand)c).Triggers
                                             where t is SpeechTrigger
                                             select t;
                        // Only initialize the recognizer if there are actual speech phrases
                        if (speechTriggers.Count() > 0) {
                            this._SpeechRecognizer = new SpeechRecognizer();
                            this._SpeechRecognizer.SpeechRecognized += new EventHandler<SpeechRecognizedEventArgs>(_SpeechRecognizer_SpeechRecognized);
                            this._SpeechRecognizer.StateChanged += new EventHandler<StateChangedEventArgs>(_SpeechRecognizer_StateChanged);
                            GrammarBuilder grammar = new GrammarBuilder();
                            Choices phrases = new Choices();

                            foreach (SpeechTrigger t in speechTriggers) {
                                phrases.Add(t.Phrase);
                            }

                            grammar.Append(phrases);
                            this._SpeechRecognizer.LoadGrammar(new Grammar(grammar));

                            this._SpeechInitialized = true;
                        }
                    }
                }

                if (this._ContextMenu != null) {
                    if (this._SpeechRecognizer != null) {
                        if (this._SpeechRecognizer.State == RecognizerState.Listening) {
                            this._ContextMenu.Text = "Toggle Speech [Listening]";
                        }
                        else {
                            this._ContextMenu.Text = "Toggle Speech [Not Listening]";
                        }
                    }
                    else {
                        this._ContextMenu.Text = "Toggle Speech [No Phrases]";
                    }

                    this._ContextMenu.Checked = true;
                }
            }
            catch {
                if (this._ContextMenu != null) {
                    this._ContextMenu.Visible = false;
                }
            }
        }
        private void DisableSpeech() {
            if (this._SpeechRecognizer != null) {
                this._SpeechRecognizer.Dispose();
            }

            this._SpeechInitialized = false;

            if (this._ContextMenu != null) {
                this._ContextMenu.Checked = false;
                this._ContextMenu.Text = "Toggle Speech [Disabled]";
            }
        }
        private void _SpeechRecognizer_SpeechRecognized(object sender, SpeechRecognizedEventArgs e) {
            string phrase = e.Result.Text;

            foreach (TriggerCommand tc in Executioner.Commands["Trigger"]) {
                foreach (ITrigger t in tc.Triggers) {
                    if (t is SpeechTrigger) {
                        if (((SpeechTrigger)t).Phrase == phrase) {
                            tc.Execute(string.Empty, true);
                            break;
                        }
                    }
                }
            }
        }
        private void _SpeechRecognizer_StateChanged(object sender, StateChangedEventArgs e) {
            if (this._ContextMenu != null) {
                if (e.RecognizerState == RecognizerState.Stopped) {
                    this._ContextMenu.Text = "Toggle Speech [Not Listening]";
                }
                else {
                    this._ContextMenu.Text = "Toggle Speech [Listening]";
                }
            }
        }

        #region ILoader
        public void Load() {
            Executioner.Loading += this._LoadingEvent;
            Executioner.Loaded += this._LoadedEvent;

            this.CallStrings = new List<string>();
            string callString = Executioner.Config.GetString("Speech", "Call String", "speech");
            this.CallStrings.Add(callString);
        }
        public void Unload() {
            Executioner.Loading -= this._LoadingEvent;
            Executioner.Loaded -= this._LoadedEvent;

            this.DisableSpeech();
            this._ContextMenu = null;
        }
        #endregion

        #region IExecutable
        public List<string> CallStrings { get; protected set; }
        public ExecutionResult Execute(string parameters) {            
            if (parameters.IsEmpty()) {
                this.ToggleSpeech();
            }
            else if (parameters == "on" || parameters == "1" || parameters == "true") {
                this.EnableSpeech();
                Executioner.Config.SetBool("Speech", "Enable Speech", false);
            }
            else if (parameters == "off" || parameters == "0" || parameters == "false") {
                this.DisableSpeech();
                Executioner.Config.SetBool("Speech", "Enable Speech", false);
            }
            else {
                return ExecutionResult.Failure;
            }

            return ExecutionResult.Success;
        }
        #endregion

        #region IConfigurable<IniFile>
        public IConfigurator<IniFile> Configurator {
            get {
                if (this._Configurator == null) {
                    this._Configurator = new SpeechConfigurator();
                }

                return this._Configurator;
            }
        }
        #endregion
    }
}
